export interface IRVOArrayItem {
    rvoIndex: number;
}

const MAX_SIZE = 1 << 30;

export class RVOArray<T extends IRVOArrayItem> {
    private _length: number = 0;
    private _array: Array<T> = [];

    get length(): number {
        return this._length;
    }
    set length(len: number) {
        if (len === this._length) return;
        if (len < this._length) {
            this._array.fill(null, len, this._length);
        }
        this._length = len;
        let minpo2 = this.minPO2(len);
        if (minpo2 > this._array.length) {
            this._array.length = minpo2;
        }
    }

    constructor(length: number = 0) {
        this.length = length;
    }

    public get(index: number) {
        return this._array[index];
    }

    public set(index: number, value: T, syncIndex = true) {
        if (index >= this._length) return console.warn("set index out of range", index);
        this._array[index] = value;
        if (syncIndex) {
            value.rvoIndex = index;
        }
    }

    public push(value: T) {
        if (this._array.length > this._length) {
            this._length++;
        } else {
            this.length = this._length + 1;
        }
        this.set(this._length - 1, value);
    }

    pop() {
        let result: T;
        if (this._length) {
            result = this._array[this._length - 1];
            this._array[this._length - 1] = null;
            this._length--;
        }
        return result;
    }

    fill(value: T, start: number = 0, end: number = this._length) {
        this._array.fill(value, start, end);
    }

    slice(start: number = 0, end: number = this._length, out?: RVOArray<T>) {
        out = out || new RVOArray();
        out.copy(this, start, end);
        return out;
    }

    remove(index: number) {
        if (!(index >= 0 && index < this._length)) {
            return console.warn("remove index out of range", index);
        }
        this.set(index, this._array[this._length - 1]);
        this._array[this._length - 1] = null;
        this._length--;
        return this._array[index];
    }

    private minPO2(c: number) {
        let n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAX_SIZE) ? MAX_SIZE : n + 1;
    }

    public copy(array: RVOArray<T> | Array<T>, start: number = 0, end: number = array.length) {
        this.length = end - start;
        if (array instanceof RVOArray) {
            for (let i = 0; i < this.length; i++) {
                this.set(i, array.get(i + start), false);
            }
        } else {
            for (let i = 0; i < this.length; i++) {
                this.set(i, array[i + start], false);
            }
        }
    }
}
